package com.cheer.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cheer.common.Constant;
import com.cheer.common.ResultEnum;
import com.cheer.dao.AccessTokenCacheDao;
import com.cheer.dao.ExMatchUserDao;
import com.cheer.dao.MallAwardDao;
import com.cheer.dao.PrizeCacheDao;
import com.cheer.dto.OaUserRegisterDTO;
import com.cheer.entity.ExMatchUserEntity;
import com.cheer.entity.MallAwardEntity;
import com.cheer.entity.OaLearnClassEntity;
import com.cheer.entity.OaUserEntity;
import com.cheer.enums.OaIntegralRuleEnum;
import com.cheer.exception.ModifiedException;
import com.cheer.exception.ServiceException;
import com.cheer.service.*;
import com.cheer.vo.ShareTicketVO;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Stopwatch;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpSession;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author cheer
 */
@Service
@Slf4j
public class AuthServiceImpl implements AuthService, ApplicationListener<ApplicationReadyEvent> {

    @Autowired
    private OaLearnClassService oaLearnClassService;

    @Autowired
    private OaIntegralService oaIntegralService;

    @Autowired
    private ThreadPoolTaskScheduler taskScheduler;

    @Value("${match.start}")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date startTime;

    @Autowired
    private PrizeCacheDao prizeCacheDao;

    @Autowired
    private ExMatchUserDao exMatchUserDao;

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {

        // TODO 上线的时候切换回来 上线的时候切换回来  上线的时候切换回来
        if (dev) {
            System.out.println("++++测试环境不创建token++++");
            System.out.println("++++测试环境不创建token++++");
            return;
        }
        taskScheduler.scheduleWithFixedDelay(() -> {
                    try {
                        String accessToken = getAppAccessToken();
                        if (StringUtils.isBlank(accessToken)) {
                            log.error("accessToken:null");
                        }
                        accessTokenCacheDao.setAccessToken(accessToken);

                        String ticket = getAppTicket(accessToken);
                        if (StringUtils.isBlank(ticket)) {
                            log.error("ticket:null");
                        }
                        accessTokenCacheDao.setTicket(ticket);
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                },
                5 * 60 * 1000);
        taskScheduler.scheduleWithFixedDelay(() -> {
                    try {
                        List<MallAwardEntity> awardList = mallAwardDao.selectList(new QueryWrapper<>()).stream()
                                .sorted(Comparator.comparingInt(MallAwardEntity::getStock).reversed())
                                .collect(Collectors.toList());

                        int sum = 0;
                        for (MallAwardEntity award : awardList) {
                            sum += award.getStock();
                        }
                        if (sum <= 0) {
                            return;
                        }

                        int random = RandomUtil.randomInt(0, sum);
                        int current = 0;
                        for (MallAwardEntity award : awardList) {
                            if (award.getStock() + current > random) {
                                prizeCacheDao.addPrize(award.getId());

                                MallAwardEntity updateAward = new MallAwardEntity();
                                updateAward.setId(award.getId());
                                updateAward.setStock(award.getStock() - 1);
                                mallAwardDao.updateById(updateAward);

                                break;
                            } else {
                                current += award.getStock();
                            }
                        }
                    } catch (Exception e) {
                        log.error(e.getMessage(), e);
                    }
                },
                1000 * 1000);
    }

    @Autowired
    private MallAwardDao mallAwardDao;

    @Value("${weixin.appId}")
    private String appid;

    @Value("${weixin.appSecret}")
    private String secret;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private OaUserService oaUserService;

    @Autowired
    private AccessTokenCacheDao accessTokenCacheDao;

    public static Boolean dev;

    @Value("${spring.profiles.active}")
    public void setDev(String devStr) {
        if("dev".equals(devStr)){
            dev = true;
        }else{
            dev = false;
        }
    }

    @Override
    public String login(String code, HttpSession session) {

        /*if(dev){
            session.setAttribute(Constant.SESSION_USERID, 38);
            session.setAttribute(Constant.SESSION_OPENID, "odBjpjl3sw4fojL1PlwZVSHIQ9h8");
            return "";
        }*/
        Integer userId ;
        String openId = "";
        AccessTokenResponse accessToken = getAccessTokenByCode(code);
        log.info(" ++++++ accessToken:{}", accessToken);
        openId = accessToken.getOpenid();
        log.info(" ++++++ openId:{}", openId);
        //TODO 这里是根据openid 查询redis中的hash中是否有userId，若没有就是注册用户
        userId = oaUserService.getIdByOpenid(openId);
        log.info(" ++++++ userId:{}", userId);
        if (userId == null) {
            // 查询用户信息，是否关注公众号，头像
            String appAccessToken = accessTokenCacheDao.getAccessToken();
            log.info(" ++++++ appAccessToken:{}", appAccessToken);
            if (StringUtils.isBlank(appAccessToken)) {
                throw new ServiceException("登录失败，请重新访问");
            }
            UserInfoResponse userInfo = getUserInfoByAccessToken(appAccessToken, openId, 3);
            log.info(" ++++++ userInfo:{}", userInfo);
            if (userInfo == null) {
                throw new ServiceException("登录失败，请重新访问");
            }
            if (userInfo.getSubscribe() != 1) {
                throw new ServiceException(ResultEnum.ACCOUNTS);
            }
            session.setAttribute("nickname", userInfo.getNickname());
            session.setAttribute("headImgUrl", userInfo.getHeadimgurl());
            session.setAttribute(Constant.SESSION_OPENID,  openId);
            throw new ServiceException(ResultEnum.NOT_REGISTER);
        }else {
            oaIntegralService.add(userId, openId, OaIntegralRuleEnum.LOGIN, null);
            session.setAttribute(Constant.SESSION_USERID, userId);
            session.setAttribute(Constant.SESSION_OPENID, openId);

            String updateTime = oaUserService.getUpdateTimeById(userId);
            if (StringUtils.isNotBlank(updateTime) && startTime.getTime() / 1000 < Integer.valueOf(updateTime)) {
                return "";
            } else {
                //TODO 若手动删除oa_user中的用户数据，必须同步删除redis中的hash-openid，否则就会返回100406，跳页会出现问题
                throw new ModifiedException("");
            }
        }
    }

    @Override
    public Integer register(OaUserRegisterDTO userRegisterDTO) {
        OaLearnClassEntity learnClass = oaLearnClassService.getById(Integer.valueOf(userRegisterDTO.getDepartment()));
        if (learnClass == null) {
            throw new ServiceException("所属单位不存在");
        }

        OaUserEntity user = new OaUserEntity();
        BeanUtils.copyProperties(userRegisterDTO, user);
        user.setHeadimgurl(userRegisterDTO.getHeadImgUrl())
                .setAddTime(String.valueOf(DateUtil.currentSeconds()))
                .setUpdataTime(String.valueOf(DateUtil.currentSeconds()))
                .setPath(learnClass.getPath() + learnClass.getId() + ",");

        oaUserService.save(user);
        oaIntegralService.add(user.getId(), userRegisterDTO.getOpenid(), OaIntegralRuleEnum.LOGIN, null);

        return user.getId();
    }


    @Override
    public ExMatchUserEntity getExUserByOpenId(HttpSession session, OaUserEntity oaUser) {
        String openid = getOpenid(session);
        ExMatchUserEntity user = getExMatchUserEntity(openid, oaUser);
        return user;
    }

    @Override
    public ExMatchUserEntity getExUserByOpenId(HttpSession session) {
        String openid = getOpenid(session);
        OaUserEntity oaUser = oaUserService.getByOpenid(openid);
        ExMatchUserEntity user = getExMatchUserEntity(openid, oaUser);
        return user;
    }

    private ExMatchUserEntity getExMatchUserEntity(String openid, OaUserEntity oaUser) {
        ExMatchUserEntity user = exMatchUserDao.selectByOpenId(openid);
        if (user == null) {
            // 查询用户表
            if (oaUser == null) {
                throw new ServiceException("当前用户未关注公众号！");
            } else {
                // 新建用户
                user = new ExMatchUserEntity();
                user.setUsername(oaUser.getName());
                user.setOpenid(oaUser.getOpenid());
                user.setPhone(oaUser.getPhone());
                user.setProvince(oaUser.getNativePlace());
                exMatchUserDao.insert(user);
            }
        }
        return user;
    }


    @Override
    public Integer getUserId(HttpSession session) {

        /*if(dev){
            return 39;
        }*/
        if (session.getAttribute(Constant.SESSION_USERID) == null) {
            throw new ServiceException("会话已超时，请重新登录");
        }
        return (Integer) session.getAttribute(Constant.SESSION_USERID);
    }

    @Override
    public String getOpenid(HttpSession session) {
        /*if(dev){
            return "odBjpjl1-0mjW5Gjc8YtCvBYHu8c";
        }*/
        if (session.getAttribute(Constant.SESSION_OPENID) == null) {
            throw new ServiceException("会话已超时，请重新登录");
        }
        return (String) session.getAttribute(Constant.SESSION_OPENID);
    }

    @Override
    public String getNickname(HttpSession session) {
        if (session.getAttribute("nickname") == null) {
            throw new ServiceException("会话已超时，请重新登录");
        }
        return (String) session.getAttribute("nickname");
    }

    @Override
    public String getHeadImgUrl(HttpSession session) {
        if (session.getAttribute("headImgUrl") == null) {
            throw new ServiceException("会话已超时，请重新登录");
        }
        return (String) session.getAttribute("headImgUrl");
    }

    @Override
    public ShareTicketVO sign() {
        String ticket = accessTokenCacheDao.getTicket();
        String url = "http://tsw.ithyxy.com/malltest/activityInfo?id=2";
        String nonce_str = create_nonce_str();
        String timestamp = create_timestamp();
        String string1;
        String signature = "";

        //注意这里参数名必须全部小写，且必须有序
        string1 = "jsapi_ticket=" + ticket +
                "&noncestr=" + nonce_str +
                "&timestamp=" + timestamp +
                "&url=" + url;
        try
        {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(string1.getBytes("UTF-8"));
            signature = byteToHex(crypt.digest());
        }
        catch (NoSuchAlgorithmException e)
        {
            e.printStackTrace();
        }
        catch (UnsupportedEncodingException e)
        {
            e.printStackTrace();
        }
        ShareTicketVO shareTicketVO = new ShareTicketVO();
        shareTicketVO.setAppId(appid);
        shareTicketVO.setTimestamp(Long.valueOf(timestamp));
        shareTicketVO.setNonceStr(nonce_str);
        shareTicketVO.setSignature(signature);
        return shareTicketVO;
    }

    private static String byteToHex(final byte[] hash) {
        Formatter formatter = new Formatter();
        for (byte b : hash)
        {
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }

    private static String create_nonce_str() {
        return UUID.randomUUID().toString();
    }

    private static String create_timestamp() {
        return Long.toString(System.currentTimeMillis() / 1000);
    }



    @Override
    public ShareTicketVO share(String url) {
        try {
            url = URLDecoder.decode(url, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(), e);
        }

        String ticket = accessTokenCacheDao.getTicket();
        String noncestr = UUID.randomUUID().toString(true).substring(0, 16);
        long timestamp = System.currentTimeMillis() / 1000;
        // 将参数排序并拼接字符串
        String str = "jsapi_ticket=" + ticket + "&noncestr=" + noncestr + "&timestamp=" + timestamp + "&url=" + url;
        String signature = DigestUtils.sha1Hex(str);

        ShareTicketVO shareTicketVO = new ShareTicketVO();
        shareTicketVO.setAppId(appid);
        shareTicketVO.setTimestamp(timestamp);
        shareTicketVO.setNonceStr(noncestr);
        shareTicketVO.setSignature(signature);
        return shareTicketVO;
    }

    private AccessTokenResponse getAccessTokenByCode(String code) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        String url = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&" +
                "code=%s&grant_type=authorization_code", appid, secret, code);

        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                AccessTokenResponse response = objectMapper.readValue(responseEntity.getBody(), AccessTokenResponse.class);
                if (response.getAccess_token() != null && response.getOpenid() != null) {
                    return response;
                }
            }
            log.warn("get access token failed, response: {}, code {}  cost {}", responseEntity, code, stopwatch.elapsed(TimeUnit.MILLISECONDS));
        } catch (Exception e) {
            log.warn("get access token failed", e);
        }
        throw new ServiceException("登录失败，请重新访问");
    }

    private String getAppAccessToken() {
        String url = String.format("https://api.weixin.qq.com/cgi-bin/token?appid=%s&secret=%s&" +
                "grant_type=client_credential", appid, secret);

        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                AccessTokenResponse response = objectMapper.readValue(responseEntity.getBody(), AccessTokenResponse.class);
                if (response.getAccess_token() != null) {
                    return response.getAccess_token();
                }
            }
            log.error("get access token failed, response: {}", responseEntity);
        } catch (Exception e) {
            log.error("get access token failed", e);
        }
        return null;
    }

    private String getAppTicket(String accessToken) {
        String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=" + accessToken + "&type=jsapi";

        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                TicketResponse response = objectMapper.readValue(responseEntity.getBody(), TicketResponse.class);
                if (response.getTicket() != null) {
                    return response.getTicket();
                }
            }
            log.error("get ticket failed, response: {}", responseEntity);
        } catch (Exception e) {
            log.error("get ticket failed", e);
        }
        return null;
    }

    private UserInfoResponse getUserInfoByAccessToken(String accessToken, String openid, Integer retryCount) {
        Stopwatch stopwatch = Stopwatch.createStarted();
        String url = String.format("https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN",
                accessToken, openid);

        try {
            ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class);
            if (responseEntity.getStatusCodeValue() == 200 && responseEntity.getBody() != null) {
                UserInfoResponse response = objectMapper.readValue(responseEntity.getBody(), UserInfoResponse.class);
                if (response.getSubscribe() != null) {
                    return response;
                } else if (retryCount > 0) {
                    return getUserInfoByAccessToken(accessToken, openid, retryCount - 1);
                }
            } else {
                log.warn("get user info failed, code {}  cost {}", responseEntity, stopwatch.elapsed(TimeUnit.MILLISECONDS));
            }
        } catch (Exception e) {
            log.error("get user info failed", e);
        }
        return null;
    }

    @Data
    private static class AccessTokenResponse {

        private String access_token;

        private Integer expires_in;

        private String refresh_token;

        private String openid;

        private String scope;
    }

    @Data
    private static class TicketResponse {

        private String errcode;

        private String errmsg;

        private String ticket;

        private Integer expires_in;
    }

    @Data
    private static class UserInfoResponse {

        private Integer subscribe;

        private String openid;

        private String nickname;

        private Integer sex;

        private String language;

        private String headimgurl;
    }

}
